package org.origin.centre.support.captcha;

import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 验证码
 *
 * @author ferret
 * @version 2024-05-08
 */
public class Captcha {
    private static final Logger LOGGER = Logger.getLogger(Captcha.class.getName());
    private static final int FONT_SIZE = 24;
    private static final int TEXT_MARGIN = 8;
    private static final Color BACKGROUND_COLOR = Color.WHITE;
    private static final Color TEXT_COLOR = Color.BLACK;
    private static final Font FONT = new Font("Times New Roman", Font.BOLD, FONT_SIZE);
    private static final int WIDTH = FONT_SIZE * 4 + TEXT_MARGIN * 5; // 假设的宽度
    private static final int HEIGHT = 30; // 假设的高度
    private static final int LINE_WIDTH = 1;
    private static final int COUNT = 200; // 假设的干扰线数量

    /**
     * 根据结果代码生成Base64编码的图片字符串。
     *
     * @param resultCode 用于生成图片的结果代码。
     * @return 返回包含Base64编码图片数据的字符串，该字符串可以直接用于HTML中的<img>标签。
     * @throws RuntimeException 如果图像处理过程中发生I/O错误。
     */
    public String generate(String resultCode) throws RuntimeException {
        // 根据结果代码获取BufferedImage对象
        BufferedImage image = this.getImageBuffer(resultCode);
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();

        // 将图像写入 ByteArrayOutputStream 中，格式为JPEG
        try {
            ImageIO.write(image, "JPEG", byteStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 从 ByteArrayOutputStream 中获取字节数组
        // 转换成字节
        byte[] bytes = byteStream.toByteArray();

        // 将字节数组编码为Base64字符串，并移除换行符
        // 转换成base64串
        String base64 = Base64.getEncoder().encodeToString(bytes).trim();
        // 删除 \r\n
        base64 = base64.replaceAll("\n", "").replaceAll("\r", "");

        // 构造包含Base64编码图片数据的data URL字符串
        return "data:image/jpg;base64," + base64;
    }

    private BufferedImage getImageBuffer(String resultCode) {
        BufferedImage bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);

        Graphics2D graphics = this.getGraphics(bufferedImage);

        SecureRandom random = initSecureRandom();

        // 生成干扰线
        drawRandomLines(graphics, random);

        // 在图象上绘制验证码
        drawCode(graphics, resultCode);

        // 图象生效并返回
        graphics.dispose();

        return bufferedImage;
    }

    private Graphics2D getGraphics(BufferedImage imageCache) {
        Graphics2D graphics = imageCache.createGraphics();
        graphics.setColor(BACKGROUND_COLOR);
        graphics.fillRect(0, 0, WIDTH, HEIGHT);
        graphics.drawRect(0, 0, WIDTH - 1, HEIGHT - 1);
        return graphics;
    }

    /**
     * 初始化SecureRandom实例。
     * <p>
     * SecureRandom是Java中用于生成安全随机数的类。此方法尝试创建一个使用SHA1PRNG算法的SecureRandom实例。
     * 如果指定的算法不可用，它将回退到使用默认的SecureRandom算法。
     *
     * @return SecureRandom实例，使用SHA1PRNG算法或默认算法。
     */
    private SecureRandom initSecureRandom() {
        SecureRandom random;
        // 尝试获取指定算法的SecureRandom实例
        try {
            random = SecureRandom.getInstance("SHA1PRNG");
        } catch (NoSuchAlgorithmException e) {
            // 如果指定算法不可用，记录警告日志并使用默认算法
            LOGGER.log(Level.WARNING, "SHA1PRNG算法不可用，使用默认的SecureRandom", e);
            random = new SecureRandom();
        }
        return random;
    }

    /**
     * 使用随机数生成若干随机线条，增加图像的复杂度，用于验证码生成。
     *
     * @param random 用于生成随机数的SecureRandom对象，确保线条位置的随机性。
     */
    private void drawRandomLines(Graphics2D graphics, SecureRandom random) {
        // 循环COUNT次，绘制随机线条
        for (int i = 0; i < COUNT; i++) {
            // 随机生成线条颜色，颜色值在150到200之间
            graphics.setColor(getRandColor(150, 200));

            // 随机生成线条起始点的x坐标
            final int x = random.nextInt(WIDTH - LINE_WIDTH - 1) + 1;
            // 随机生成线条起始点的y坐标
            final int y = random.nextInt(HEIGHT - LINE_WIDTH - 1) + 1;
            // 随机生成线条的长度
            final int xl = random.nextInt(LINE_WIDTH);
            // 随机生成线条的宽度
            final int yl = random.nextInt(LINE_WIDTH);

            // 绘制线条，从(x, y)点到(x+xl, y+yl)点
            graphics.drawLine(x, y, x + xl, y + yl);
        }
    }

    /**
     * 绘制验证码。
     * 使用Graphics2D对象在缓存图像上绘制验证码字符串。每个字符在X轴上以固定间隔排列，Y轴位置固定。
     * 此方法不返回任何值，但它修改了imageCache对象，绘制了验证码文本。
     *
     * @param resultCode 验证码字符串，包含要绘制的字符。
     */
    private void drawCode(Graphics2D graphics, String resultCode) {
        // 设置文本颜色
        graphics.setColor(TEXT_COLOR);
        // 设置文本字体
        graphics.setFont(FONT);

        // 遍历验证码字符串中的每个字符，并绘制到图像上
        for (int i = 0; i < resultCode.length(); i++) {
            // 计算字符的绘制位置，并调用drawString方法进行绘制
            graphics.drawString(String.valueOf(resultCode.charAt(i)), (FONT_SIZE * i) + TEXT_MARGIN * (i + 1), FONT_SIZE);
        }
    }

    /**
     * 生成一个介于给定的起点色和终点色之间的随机颜色。
     *
     * @param fc 起点色的整数值，范围在0到255之间。
     * @param bc 终点色的整数值，范围在0到255之间。
     * @return 一个随机生成的颜色对象，其RGB值介于fc和bc之间。
     */
    private Color getRandColor(int fc, int bc) {
        // 创建一个随机数生成器实例
        // 此方法实现省略，假定它返回一个在fc和bc之间的随机颜色。
        final Random random = new Random();
        // 定义颜色的最大值
        int length = 255;
        // 确保起点色和终点色不超过最大值
        if (fc > length) {
            fc = length;
        }
        if (bc > length) {
            bc = length;
        }

        // 生成随机的红、绿、蓝分量，确保它们在fc和bc之间
        final int r = fc + random.nextInt(bc - fc);
        final int g = fc + random.nextInt(bc - fc);
        final int b = fc + random.nextInt(bc - fc);

        // 返回一个新的颜色对象，其RGB值由随机生成的红、绿、蓝分量确定
        return new Color(r, g, b);
    }

}
